package pl.dsp.dsp1.ui;

import java.awt.Component;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.FileWriter;
import java.util.Collections;

import javax.swing.JButton;
import javax.swing.JFileChooser;
import javax.swing.JOptionPane;
import javax.swing.ListModel;

import org.json.simple.JSONObject;
import org.json.simple.JSONValue;

import com.panayotis.gnuplot.GNUPlot;
import com.panayotis.gnuplot.plot.DataSetPlot;
import com.panayotis.gnuplot.style.PlotStyle;
import com.panayotis.gnuplot.style.Style;
import com.panayotis.gnuplot.terminal.ImageTerminal;

import pl.dsp.dsp1.processing.AddSignalValue;
import pl.dsp.dsp1.processing.DFTGenerator;
import pl.dsp.dsp1.processing.DivideSignalValue;
import pl.dsp.dsp1.processing.FFTFactory;
import pl.dsp.dsp1.processing.FirstOrderInterpolator;
import pl.dsp.dsp1.processing.GeneratorFactory;
import pl.dsp.dsp1.processing.IDFTGenerator;
import pl.dsp.dsp1.processing.MultiplySignalValue;
import pl.dsp.dsp1.processing.Radar;
import pl.dsp.dsp1.processing.Range;
import pl.dsp.dsp1.processing.Signal;
import pl.dsp.dsp1.processing.SignalValue;
import pl.dsp.dsp1.processing.SimpleValue;
import pl.dsp.dsp1.processing.SincInterpolator;
import pl.dsp.dsp1.processing.SubstractSignalValue;
import pl.dsp.dsp1.processing.ToMagnitudePhase;
import pl.dsp.dsp1.processing.WaveletFactory;

public class WindowController implements ActionListener {

	private Radar radar;
	private MainWindow window;
	private SignalListModel signalListModel = new SignalListModel();
	private JFileChooser fileChooser = new JFileChooser();
	private FilterWindow filterWindow;
	private RadarWindow radarWindow;

	public WindowController(MainWindow window) {
		this.window = window;
	}

	public void setFilterWindow(FilterWindow filterWindow) {
		this.filterWindow = filterWindow;
	}

	private GNUPlot setupGNUPlot(double[][] data, PlotStyle style,
			Component target, int dimensions) {
		GNUPlot gnuplot = new GNUPlot();
		ImageTerminal terminal = new ImageTerminal();
		terminal.set("size", target.getWidth() + "," + target.getHeight()
				/ dimensions);
		terminal.set("enhanced");
		terminal.set("truecolor");
		gnuplot.setTerminal(terminal);
		DataSetPlot plot = new DataSetPlot(data);
		plot.setTitle("");
		plot.setPlotStyle(style);
		gnuplot.addPlot(plot);
		return gnuplot;
	}

	public void plotSignal(Signal signal, PlotStyle style, Component target) {
		double[][] data = new double[signal.size()][2];
		Double[] X = signal.getXAxis().toArray(new Double[1]);

		for (int dimension = 0; dimension < signal.getDimensions(); dimension++) {
			Double[] Y = (Double[]) signal.getYAxis(dimension).toArray(
					new Double[1]);

			for (int i = 0; i < signal.size(); i++) {
				data[i][0] = X[i];
				data[i][1] = Y[i];
			}

			GNUPlot gnuplot = setupGNUPlot(data, style, target,
					signal.getDimensions());
			gnuplot.plot();

			target.getGraphics().drawImage(
					((ImageTerminal) gnuplot.getTerminal()).getImage(), 0,
					dimension * target.getHeight() / signal.getDimensions(),
					null);
		}
	}

	public void plotSignal(Signal signal, PlotStyle style) {
		Component target = window.getGraphContainer();
		plotSignal(signal, style, target);
	}

	public void plotHistogram(Signal signal, int ranges) {
		for (int dimension = 0; dimension < signal.getDimensions(); dimension++) {
			Double[] values = (Double[]) signal.getYAxis(dimension).toArray(
					new Double[1]);

			Range valueRange = signal.getValueRange(dimension);

			// prepare ranges
			double[] X = new double[ranges + 1];
			for (int range = 0; range < ranges + 1; range++) {
				X[range] = valueRange.getStart()
						+ (valueRange.getEnd() - valueRange.getStart())
						/ ranges * range;
			}

			double[] Y = new double[ranges + 1];
			for (int i = 0; i < signal.size(); i++) {
				Y[(int) ((values[i] - valueRange.getStart())
						/ (valueRange.getEnd() - valueRange.getStart())
						* ranges * 0.999)] += 1;
			}

			double[][] data = new double[ranges + 1][2];

			for (int i = 0; i < ranges + 1; i++) {
				data[i][0] = X[i];
				data[i][1] = Y[i];
			}

			Component target = window.getGraphContainer();
			GNUPlot gnuplot = setupGNUPlot(data, new PlotStyle(Style.BOXES),
					target, signal.getDimensions());

			gnuplot.set("xtics", "offset first -"
					+ (valueRange.getEnd() - valueRange.getStart()) / ranges
					/ 2 + ",0");
			gnuplot.plot();

			target.getGraphics().drawImage(
					((ImageTerminal) gnuplot.getTerminal()).getImage(), 0,
					dimension * target.getHeight() / signal.getDimensions(),
					null);
		}
	}

	public void calculateParams(Signal signal) {
		SignalValue mean = signal.get(0);
		for (int index = 1; index < signal.size(); index++) {
			mean = mean.add(signal.get(index));
		}
		mean = mean.divide(new SimpleValue(signal.size()));

		SignalValue absoluteMean = signal.get(0).abs();
		for (int index = 1; index < signal.size(); index++) {
			absoluteMean = absoluteMean.add(signal.get(index).abs());
		}
		absoluteMean = absoluteMean.divide(new SimpleValue(signal.size()));

		SignalValue power = signal.get(0).multiply(signal.get(0));
		for (int index = 1; index < signal.size(); index++) {
			power = power.add(signal.get(index).multiply(signal.get(index)));
		}
		power = power.divide(new SimpleValue(signal.size()));

		SignalValue variance = signal.get(0).substract(mean);
		variance = variance.multiply(variance);
		for (int index = 1; index < signal.size(); index++) {
			SignalValue added = signal.get(index).substract(mean);
			added = added.multiply(added);
			variance = variance.add(added);
		}
		variance = variance.divide(new SimpleValue(signal.size()));

		SignalValue rms = new SimpleValue(Math.sqrt(power.getValues().get(0)));

		String paramsMessage = "";
		paramsMessage += "Mean: " + mean.getValues().get(0) + "\n";
		paramsMessage += "Absolute mean: " + absoluteMean.getValues().get(0)
				+ "\n";
		paramsMessage += "Power: " + power.getValues().get(0) + "\n";
		paramsMessage += "Variance: " + variance.getValues().get(0) + "\n";
		paramsMessage += "RMS: " + rms.getValues().get(0) + "\n";

		JOptionPane.showMessageDialog(null, paramsMessage);
	}

	public void calculateErrors(Signal signal1, Signal signal2) {
		double MSE = 0;
		for (int index = 0; index < signal1.size(); index++) {
			SignalValue difference = signal1.get(index).substract(
					signal2.get(index));
			MSE += difference.multiply(difference).getValues().get(0);
		}
		MSE /= signal1.size();

		double squaresSum = 0;
		for (int index = 0; index < signal1.size(); index++) {
			squaresSum += signal1.get(index).multiply(signal1.get(index))
					.getValues().get(0);
		}
		double SNR = 10 * Math.log10(squaresSum / (MSE * signal1.size()));

		double peak = Double.MIN_VALUE;
		for (int index = 0; index < signal1.size(); index++) {
			if (peak < signal1.get(index).getValues().get(0))
				peak = signal1.get(index).getValues().get(0);
		}
		double PSNR = 10 * Math.log10(peak / MSE);

		double MD = Double.MIN_VALUE;
		for (int index = 0; index < signal1.size(); index++) {
			SignalValue difference = signal1.get(index)
					.substract(signal2.get(index)).abs();
			if (MD < difference.getValues().get(0))
				MD = difference.getValues().get(0);
		}

		String errorsMessage = "";
		errorsMessage += "MSE: " + MSE + "\n";
		errorsMessage += "SNR: " + SNR + "\n";
		errorsMessage += "ENOB: " + (SNR - 1.76) / 6.02 + "\n";
		errorsMessage += "PSNR: " + PSNR + "\n";
		errorsMessage += "MD: " + MD + "\n";

		JOptionPane.showMessageDialog(null, errorsMessage);
	}

	private Signal getFirstSignal() throws Exception {
		if (window.getSelectedIndices().length == 0) {
			JOptionPane.showMessageDialog(window,
					"Please select at least one signal");
			throw new Exception();
		}

		return signalListModel.get(window.getSelectedIndices()[0]);
	}

	private Signal getSecondSignal() throws Exception {
		if (window.getSelectedIndices().length < 2) {
			JOptionPane.showMessageDialog(window, "Please select two signals");
			throw new Exception();
		}

		return signalListModel.get(window.getSelectedIndices()[1]);
	}

	@Override
	public void actionPerformed(ActionEvent event) {
		try {
			String source = ((JButton) event.getSource()).getText();

			double A = window.getA();
			double t1 = window.getT1();
			double d = window.getD();
			double T = window.getT();
			double kw = window.getKw();

			if (source == "preview") {
				double samplingPeriod = d
						/ window.getGraphContainer().getWidth();

				Signal signal = new Signal(window.getSignalSource(), A, t1, d,
						T, kw, samplingPeriod);
				plotSignal(signal, new PlotStyle(Style.LINES));
			}

			if (source == "plot") {
				plotSignal(getFirstSignal(), new PlotStyle(Style.LINES));
			}

			if (source == "push") {
				double samplingPeriod = 1.0 / window.getFp();

				Signal signal = new Signal(window.getSignalSource(), A, t1, d,
						T, kw, samplingPeriod);

				signalListModel.add(signal);
				plotSignal(signalListModel.get(signalListModel.size() - 1),
						new PlotStyle(Style.LINES));
			}

			if (source == "remove") {
				signalListModel.remove(window.getSelectedIndices()[0]);
			}

			if (source == "swap") {
				Collections.swap(signalListModel,
						window.getSelectedIndices()[0],
						window.getSelectedIndices()[1]);
				signalListModel.notifyListeners();
			}

			if (source == "rename") {
				String name = JOptionPane.showInputDialog("Signal name");
				if (name != null) {
					getFirstSignal().setName(name);
					signalListModel.notifyListeners();
				}
			}

			if (source == "save") {
				int userChoice = fileChooser.showSaveDialog(window);
				if (userChoice == JFileChooser.APPROVE_OPTION) {
					SignalBytes asBytes = new SignalBytes(getFirstSignal());
					FileOutputStream output;
					try {
						output = new FileOutputStream(
								fileChooser.getSelectedFile());
						output.write(asBytes.getBytes());
					} catch (Exception e1) {
						e1.printStackTrace();
					}
				}
			}

			if (source == "load") {
				int userChoice = fileChooser.showOpenDialog(window);
				if (userChoice == JFileChooser.APPROVE_OPTION) {
					byte[] bytes = new byte[(int) fileChooser.getSelectedFile()
							.length()];
					try {
						FileInputStream input = new FileInputStream(
								fileChooser.getSelectedFile());
						input.read(bytes, 0, bytes.length);
						SignalBytes asBytes = new SignalBytes(bytes);
						signalListModel.add(asBytes.getSignal());
						plotSignal(
								signalListModel.get(signalListModel.size() - 1),
								new PlotStyle(Style.LINES));
					} catch (Exception e) {
						e.printStackTrace();
					}
				}
			}

			if (source == "save json") {
				int userChoice = fileChooser.showSaveDialog(window);
				if (userChoice == JFileChooser.APPROVE_OPTION) {
					SignalJSON asJSON = new SignalJSON(getFirstSignal());
					try {
						FileWriter output = new FileWriter(
								fileChooser.getSelectedFile());
						output.write(asJSON.getJSON().toString());
						output.flush();
					} catch (Exception e1) {
						e1.printStackTrace();
					}
				}
			}

			if (source == "load json") {
				int userChoice = fileChooser.showOpenDialog(window);
				if (userChoice == JFileChooser.APPROVE_OPTION) {
					char[] characters = new char[(int) fileChooser
							.getSelectedFile().length()];
					try {
						FileReader input = new FileReader(
								fileChooser.getSelectedFile());
						input.read(characters);
						SignalJSON asJSON = new SignalJSON(
								(JSONObject) JSONValue.parse(new String(
										characters)));
						signalListModel.add(asJSON.getSignal());
						plotSignal(
								signalListModel.get(signalListModel.size() - 1),
								new PlotStyle(Style.LINES));
					} catch (Exception e) {
						e.printStackTrace();
					}
				}
			}

			if (source == "histogram") {
				plotHistogram(getFirstSignal(), window.getRanges());
			}

			if (source == "mag&phase") {
				signalListModel.add(getFirstSignal().perform(
						new ToMagnitudePhase(), getFirstSignal()));
				plotSignal(signalListModel.get(signalListModel.size() - 1),
						new PlotStyle(Style.LINES));
			}

			if (source == "quantize") {
				signalListModel.add(getFirstSignal().quantize(
						window.getBitdepth()));
				plotSignal(signalListModel.get(signalListModel.size() - 1),
						new PlotStyle(Style.LINES));
			}

			if (source == "1st interpolation") {
				Signal signal = getFirstSignal();
				signalListModel.add(signal.interpolate(
						new FirstOrderInterpolator(signal), window.getFp()));
				plotSignal(signalListModel.get(signalListModel.size() - 1),
						new PlotStyle(Style.LINES));
			}

			if (source == "sinc interpolation") {
				Signal signal = getFirstSignal();
				signalListModel.add(signal.interpolate(new SincInterpolator(
						signal, window.getSincSamples()), window.getFp()));
				plotSignal(signalListModel.get(signalListModel.size() - 1),
						new PlotStyle(Style.LINES));
			}

			if (source == "convolution") {
				Signal signal = getFirstSignal();
				signalListModel.add(signal.convolute(getSecondSignal()));
				plotSignal(signalListModel.get(signalListModel.size() - 1),
						new PlotStyle(Style.LINES));
			}

			if (source == "correlation") {
				Signal signal = getFirstSignal();
				signalListModel.add(signal.correlate(getSecondSignal()));
				plotSignal(signalListModel.get(signalListModel.size() - 1),
						new PlotStyle(Style.LINES));
			}

			if (source == "params") {
				calculateParams(getFirstSignal());
			}

			if (source == "errors") {
				calculateErrors(getSecondSignal(), getFirstSignal());
			}

			if (source == "add") {
				signalListModel.add(getSecondSignal().perform(
						new AddSignalValue(), getFirstSignal()));
				plotSignal(signalListModel.get(signalListModel.size() - 1),
						new PlotStyle(Style.LINES));
			}

			if (source == "substract") {
				signalListModel.add(getSecondSignal().perform(
						new SubstractSignalValue(), getFirstSignal()));
				plotSignal(signalListModel.get(signalListModel.size() - 1),
						new PlotStyle(Style.LINES));
			}

			if (source == "multiply") {
				signalListModel.add(getSecondSignal().perform(
						new MultiplySignalValue(), getFirstSignal()));
				plotSignal(signalListModel.get(signalListModel.size() - 1),
						new PlotStyle(Style.LINES));
			}

			if (source == "divide") {
				signalListModel.add(getSecondSignal().perform(
						new DivideSignalValue(), getFirstSignal()));
				plotSignal(signalListModel.get(signalListModel.size() - 1),
						new PlotStyle(Style.LINES));
			}

			// filtering

			if (source == "Infer") {
				double fp = 1.0 / getFirstSignal().getSamplingPeriod();
				filterWindow.setFp(fp);
			}

			if (source == "Lowpass") {
				double fp = filterWindow.getFp();
				double fo = filterWindow.getFo();
				int M = filterWindow.getM();
				double K = fp / fo;

				Signal result = new Signal(0, 1 / fp);

				for (int n = 0; n < M; n++) {
					if (n == (M - 1) / 2) {
						result.add(new SimpleValue(2 / K));
					} else {
						double value = Math.sin(2 * Math.PI * (n - (M - 1) / 2)
								/ K)
								/ (Math.PI * (n - (M - 1) / 2));
						result.add(new SimpleValue(value));
					}
				}

				signalListModel.add(result);
				plotSignal(signalListModel.get(signalListModel.size() - 1),
						new PlotStyle(Style.LINES));
			}

			if (source == "Blackman") {
				double fp = filterWindow.getFp();
				int M = filterWindow.getM();

				Signal result = new Signal(0, 1 / fp);

				for (int n = 0; n < M; n++) {
					double value = 0.42 - 0.5 * Math.cos(2.0 * Math.PI * n / M)
							+ 0.08 * Math.cos(4.0 * Math.PI * n / M);
					result.add(new SimpleValue(value));
				}

				signalListModel.add(result);
				plotSignal(signalListModel.get(signalListModel.size() - 1),
						new PlotStyle(Style.LINES));
			}

			if (source == "Lowpass->Bandpass") {
				double fp = filterWindow.getFp();

				double filterD = (getFirstSignal().size() - 1)
						* getFirstSignal().getSamplingPeriod();
				Signal signal = new Signal(GeneratorFactory.Source.SINE, 1.0,
						0, filterD, 1 / fp * 4, 0, 1 / fp);

				signalListModel.add(signal);
				plotSignal(signalListModel.get(signalListModel.size() - 1),
						new PlotStyle(Style.LINES));
			}

			// radar

			if (source == "Start") {
				if (radar != null)
					radar.stop();
				radar = new Radar(radarWindow.getBufferSize(),
						radarWindow.getObjectDistance(),
						radarWindow.getSignalSpeed(), radarWindow.getT(),
						radarWindow.getFp(), radarWindow.getRefreshTime());
				radar.setUpdateListener(new ActionListener() {
					@Override
					public void actionPerformed(ActionEvent e) {
						plotSignal(radar.getOriginal(), new PlotStyle(
								Style.LINES), radarWindow.getGraphContainer1());
						plotSignal(radar.getReceived(), new PlotStyle(
								Style.LINES), radarWindow.getGraphContainer2());
						plotSignal(radar.getCorrelated(), new PlotStyle(
								Style.LINES), radarWindow.getGraphContainer3());
						radarWindow.getResultLabel().setText("Distance: " + radar.getComputed());
					}
				});
			}

			if (source == "Stop") {
				if (radar != null)
					radar.stop();
			}
			
			// transformations
			
			if (source == "DFT") {
				Signal signal = getFirstSignal();
				double originalD = signal.getSamplingPeriod() * (signal.size() - 1);
				Signal transformed = new Signal(new DFTGenerator(signal), 1, 0, originalD, 1, signal.getSamplingPeriod());
				signalListModel.add(transformed);
				plotSignal(signalListModel.get(signalListModel.size() - 1),
						new PlotStyle(Style.LINES));
			}
			
			if (source == "IDFT") {
				Signal signal = getFirstSignal();
				double originalD = signal.getSamplingPeriod() * (signal.size() - 1);
				Signal transformed = new Signal(new IDFTGenerator(signal), 1, 0, originalD, 1, signal.getSamplingPeriod());
				signalListModel.add(transformed);
				plotSignal(signalListModel.get(signalListModel.size() - 1),
						new PlotStyle(Style.LINES));
			}
			
			if (source == "FFT") {
				Signal signal = getFirstSignal();
				signalListModel.add(new FFTFactory(signal).getFFT());
				plotSignal(signalListModel.get(signalListModel.size() - 1),
						new PlotStyle(Style.LINES));
			}
			
			if (source == "IFFT") {
				Signal signal = getFirstSignal();
				signalListModel.add(new FFTFactory(signal).getIFFT());
				plotSignal(signalListModel.get(signalListModel.size() - 1),
						new PlotStyle(Style.LINES));
			}
			
			if (source == "Wavelet") {
				Signal signal = getFirstSignal();
				signalListModel.add(new WaveletFactory(signal).getSimpleTransform(WaveletFactory.H8, WaveletFactory.G8));
				plotSignal(signalListModel.get(signalListModel.size() - 1),
						new PlotStyle(Style.LINES));
			}
			
			if (source == "Inverse Wavelet") {
				Signal signal = getFirstSignal();
				signalListModel.add(new WaveletFactory(signal).getInverseTransform(WaveletFactory.H8, WaveletFactory.G8));
				plotSignal(signalListModel.get(signalListModel.size() - 1),
						new PlotStyle(Style.LINES));
			}
		} catch (ArrayIndexOutOfBoundsException e) {
			if (window.getSelectedIndices().length < 1) {
				JOptionPane.showMessageDialog(window,
						"Please select at least on signal");

			} else if (window.getSelectedIndices().length < 2) {
				JOptionPane.showMessageDialog(window,
						"Please select two signals");
			}
		} catch (Exception e) {

		}
	}

	public ListModel getSignalListModel() {
		return signalListModel;
	}

	public void setRadarWindow(RadarWindow radarWindow) {
		this.radarWindow = radarWindow;
	}
}
